package ib080542d;

import java.util.ArrayList;
import java.util.List;

import rs.etf.pp1.symboltable.concepts.Obj;
import rs.etf.pp1.symboltable.concepts.Scope;
import rs.etf.pp1.symboltable.concepts.Struct;
import rs.etf.pp1.symboltable.visitors.SymbolTableVisitor;

/**
 * 
 * @author Bozo Ivetic
 * 
 */
public class DumpSymbolTable extends SymbolTableVisitor {

	protected StringBuilder output = new StringBuilder();
	protected final String indent = "   ";
	protected StringBuilder currentIndent = new StringBuilder();
	protected boolean isRef = false;
	protected Obj objForType = null;
	List<Obj> userDefinedTypes = new ArrayList<>();

	protected void nextIndentationLevel() {
		currentIndent.append(indent);
	}

	protected void previousIndentationLevel() {
		if (currentIndent.length() > 0)
			currentIndent.setLength(currentIndent.length() - indent.length());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * rs.etf.pp1.symboltable.test.SymbolTableVisitor#visitObjNode(symboltable
	 * .Obj)
	 */
	@Override
	public void visitObjNode(Obj objToVisit) {
		// output.append("[");
		if (objToVisit.getName().equals("this")
				|| ((objToVisit.getType().getKind() == Struct.Class || objToVisit
						.getType().getKind() == Struct.Array) && objToVisit
						.getKind() != Obj.Type)) {
			isRef = true;
		} else {
			isRef = false;
		}
		objForType = objToVisit;
		switch (objToVisit.getKind()) {
		case Obj.Con:
			output.append("Con ");
			break;
		case Obj.Var:
			output.append("Var ");
			break;
		case Obj.Type:
			output.append("Type ");
			userDefinedTypes.add(objToVisit);
			break;
		case Obj.Meth:
			output.append("Meth ");
			break;
		case Obj.Fld:
			output.append("Fld ");
			break;
		case Obj.Prog:
			output.append("Prog ");
			break;
		}

		output.append(objToVisit.getName());
		output.append(": ");

		objToVisit.getType().accept(this);

		output.append(", ");
		output.append(objToVisit.getAdr());
		output.append(", ");
		output.append(objToVisit.getLevel() + "\n");
		if (objToVisit.getKind() == Obj.Prog
				|| objToVisit.getKind() == Obj.Meth) {
			//output.append("\n");
			nextIndentationLevel();
		}

		for (Obj o : objToVisit.getLocalSymbols()) {
			output.append(currentIndent.toString());
			o.accept(this);
			//output.append("\n");
		}

		if (objToVisit.getKind() == Obj.Prog
				|| objToVisit.getKind() == Obj.Meth)
			previousIndentationLevel();

		// output.append("]");

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * rs.etf.pp1.symboltable.test.SymbolTableVisitor#visitScopeNode(symboltable
	 * .Scope)
	 */
	@Override
	public void visitScopeNode(Scope scope) {
		for (Obj o : scope.values()) {
			o.accept(this);
			//output.append("\n");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * rs.etf.pp1.symboltable.test.SymbolTableVisitor#visitStructNode(symboltable
	 * .Struct)
	 */
	@Override
	public void visitStructNode(Struct structToVisit) {
		Obj userType = null;
		for (Obj o : userDefinedTypes) {
			if (o.getType().equals(structToVisit)) {
				userType = o;
			}
			
			
		}
		switch (structToVisit.getKind()) {
		case Struct.None:
			output.append("notype");
			break;
		case Struct.Int:
			if(userType.getName().equals("bool")) {
				output.append("bool");
			}
			else {
				output.append("int");
			}
			break;
		case Struct.Char:
			output.append("char");
			break;
		case Struct.Array:
			if (userType != null && userType.getName().equals("string")) {
				output.append("string");
			} else {
				output.append("Arr of ");
				switch (structToVisit.getElemType().getKind()) {
				case Struct.None:
					output.append("notype");
					break;
				case Struct.Int:
					for (Obj o : userDefinedTypes) {
						if (o.getType().equals(structToVisit.getElemType())) {
							userType = o;
						}
					}
					if(userType != null && userType.getName().equals("bool")) {
						output.append("bool");
					}
					else {
						output.append("int");
					}
					break;
				case Struct.Char:
					
					output.append("char");
					break;
				case Struct.Class:
					// output.append("Class");
					if (userType != null) {
						output.append(userType.getName());
					}
					else {	//slucaj kada imamo niz objekata klase
						for (Obj o : userDefinedTypes) {
							if (o.getType().equals(structToVisit.getElemType())) {
								userType = o;
							}
						}
						if(userType != null) {
							output.append(userType.getName());
						}
					}
					break;
				case Struct.Array:
					output.append("string");
					break;
				}
			}
			break;
		case Struct.Class:

			if (!isRef) {
				output.append("Class");
				output.append("\n");
				output.append(currentIndent.toString());
				output.append("[");
				output.append("\n");
				nextIndentationLevel();
				for (Obj obj : structToVisit.getMembers().symbols()) {
					output.append(currentIndent.toString());
					obj.accept(this);
					//output.append("\n");
				}
				previousIndentationLevel();
				output.append(currentIndent.toString());
				output.append("]");
			} else {
				if (userType != null) {
					output.append(userType.getName());
				}
				else {
					output.append("Class []");
					
				}
			}
			break;
		}

	}

	public String getOutput() {
		return output.toString();
	}
}
